// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/posix/file_descriptor_shuffle.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// 'Duplicated' file descriptors start at this number
const int kDuplicateBase = 1000;

} // namespace

namespace base {

struct Action {
    enum Type {
        CLOSE,
        MOVE,
        DUPLICATE,
    };

    Action(Type in_type, int in_fd1, int in_fd2 = -1)
        : type(in_type)
        , fd1(in_fd1)
        , fd2(in_fd2)
    {
    }

    bool operator==(const Action& other) const
    {
        return other.type == type && other.fd1 == fd1 && other.fd2 == fd2;
    }

    Type type;
    int fd1;
    int fd2;
};

class InjectionTracer : public InjectionDelegate {
public:
    InjectionTracer()
        : next_duplicate_(kDuplicateBase)
    {
    }

    bool Duplicate(int* result, int fd) override
    {
        *result = next_duplicate_++;
        actions_.push_back(Action(Action::DUPLICATE, *result, fd));
        return true;
    }

    bool Move(int src, int dest) override
    {
        actions_.push_back(Action(Action::MOVE, src, dest));
        return true;
    }

    void Close(int fd) override { actions_.push_back(Action(Action::CLOSE, fd)); }

    const std::vector<Action>& actions() const { return actions_; }

private:
    int next_duplicate_;
    std::vector<Action> actions_;
};

TEST(FileDescriptorShuffleTest, Empty)
{
    InjectiveMultimap map;
    InjectionTracer tracer;

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, Noop)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 0, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, NoopAndClose)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 0, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    EXPECT_EQ(0u, tracer.actions().size());
}

TEST(FileDescriptorShuffleTest, Simple1)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(1u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
}

TEST(FileDescriptorShuffleTest, Simple2)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));
    map.push_back(InjectionArc(2, 3, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(2u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 2, 3));
}

TEST(FileDescriptorShuffleTest, Simple3)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(2u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, Simple4)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(10, 0, true));
    map.push_back(InjectionArc(1, 1, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(2u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 10, 0));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::CLOSE, 10));
}

TEST(FileDescriptorShuffleTest, Cycle)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));
    map.push_back(InjectionArc(1, 0, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(4u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::DUPLICATE, kDuplicateBase, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
    EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose1)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, true));
    map.push_back(InjectionArc(1, 0, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(4u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::DUPLICATE, kDuplicateBase, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
    EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose2)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));
    map.push_back(InjectionArc(1, 0, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(4u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::DUPLICATE, kDuplicateBase, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
    EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, CycleAndClose3)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, true));
    map.push_back(InjectionArc(1, 0, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(4u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::DUPLICATE, kDuplicateBase, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::MOVE, kDuplicateBase, 0));
    EXPECT_TRUE(tracer.actions()[3] == Action(Action::CLOSE, kDuplicateBase));
}

TEST(FileDescriptorShuffleTest, Fanout)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));
    map.push_back(InjectionArc(0, 2, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(2u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose1)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, true));
    map.push_back(InjectionArc(0, 2, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(3u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose2)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, false));
    map.push_back(InjectionArc(0, 2, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(3u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

TEST(FileDescriptorShuffleTest, FanoutAndClose3)
{
    InjectiveMultimap map;
    InjectionTracer tracer;
    map.push_back(InjectionArc(0, 1, true));
    map.push_back(InjectionArc(0, 2, true));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &tracer));
    ASSERT_EQ(3u, tracer.actions().size());
    EXPECT_TRUE(tracer.actions()[0] == Action(Action::MOVE, 0, 1));
    EXPECT_TRUE(tracer.actions()[1] == Action(Action::MOVE, 0, 2));
    EXPECT_TRUE(tracer.actions()[2] == Action(Action::CLOSE, 0));
}

class FailingDelegate : public InjectionDelegate {
public:
    bool Duplicate(int* result, int fd) override { return false; }

    bool Move(int src, int dest) override { return false; }

    void Close(int fd) override { }
};

TEST(FileDescriptorShuffleTest, EmptyWithFailure)
{
    InjectiveMultimap map;
    FailingDelegate failing;

    EXPECT_TRUE(PerformInjectiveMultimap(map, &failing));
}

TEST(FileDescriptorShuffleTest, NoopWithFailure)
{
    InjectiveMultimap map;
    FailingDelegate failing;
    map.push_back(InjectionArc(0, 0, false));

    EXPECT_TRUE(PerformInjectiveMultimap(map, &failing));
}

TEST(FileDescriptorShuffleTest, Simple1WithFailure)
{
    InjectiveMultimap map;
    FailingDelegate failing;
    map.push_back(InjectionArc(0, 1, false));

    EXPECT_FALSE(PerformInjectiveMultimap(map, &failing));
}

} // namespace base
